package com.yeskery.nut.test.junit;

import com.yeskery.nut.annotation.bean.Qualifier;
import com.yeskery.nut.annotation.test.NutBootTest;
import com.yeskery.nut.application.*;
import com.yeskery.nut.bean.BaseApplicationContext;
import com.yeskery.nut.bean.NoSuchBeanException;
import com.yeskery.nut.core.NutException;
import com.yeskery.nut.util.BeanUtils;
import com.yeskery.nut.util.StringUtils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.extension.*;

import java.lang.reflect.Parameter;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Nut Junit扩展
 * @author Yeskery
 * 2023/8/17
 */
public class NutExtension implements BeforeAllCallback, AfterAllCallback, TestInstancePostProcessor, ParameterResolver {

    /** 日志对象 */
    private static final Logger LOGGER = Logger.getLogger(NutExtension.class.getName());

    /** Nut扩展命名空间 */
    private static final ExtensionContext.Namespace TEST_CONTEXT_NAMESPACE = ExtensionContext.Namespace.create(NutExtension.class);

    @Override
    public void afterAll(ExtensionContext extensionContext) throws Exception {
        getNutApplication(extensionContext).close();
    }

    @Override
    public void beforeAll(ExtensionContext extensionContext) throws Exception {
        Assertions.assertNotNull(getNutApplication(extensionContext), "Nut Application Failed To Initialized.");
    }

    @Override
    public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
        Parameter parameter = parameterContext.getParameter();
        String name = null;
        if (parameter.isAnnotationPresent(Qualifier.class)) {
            name = parameter.getAnnotation(Qualifier.class).value();
        }
        try {
            return BeanUtils.getDependBeanObject(getNutApplication(extensionContext).getApplicationContext(),
                    name, parameter.getParameterizedType()) != null;
        } catch (NoSuchBeanException e) {
            return false;
        }
    }

    @Override
    public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
        Parameter parameter = parameterContext.getParameter();
        String name = null;
        if (parameter.isAnnotationPresent(Qualifier.class)) {
            name = parameter.getAnnotation(Qualifier.class).value();
        }
        try {
            return BeanUtils.getDependBeanObject(getNutApplication(extensionContext).getApplicationContext(),
                    name, parameter.getParameterizedType());
        } catch (NoSuchBeanException e) {
            // Not Happen.
            return null;
        }
    }

    @Override
    public void postProcessTestInstance(Object o, ExtensionContext extensionContext) throws Exception {
        ((BaseApplicationContext) (getNutApplication(extensionContext).getApplicationContext())).beanAttributePadding(o);
    }

    /**
     * 获取Nut应用对象
     * @param context 扩展上下文
     * @return Nut应用对象
     */
    static NutApplication getNutApplication(ExtensionContext context) {
        if (context == null) {
            throw new NutException("ExtensionContext must not be null");
        }
        Class<?> testClass = context.getRequiredTestClass();
        ExtensionContext.Store store = getStore(context);
        return store.getOrComputeIfAbsent(testClass, c -> {
            NutBootTest nutBootTest = testClass.getAnnotation(NutBootTest.class);
            ApplicationType applicationType = ApplicationType.TEST;
            if (nutBootTest != null && nutBootTest.applicationType() != null) {
                if (ApplicationType.isTestApplicationType(nutBootTest.applicationType())) {
                    applicationType = nutBootTest.applicationType();
                } else {
                    LOGGER.warning("Invalid ApplicationType [" + applicationType
                            + "] In Test Environment, Now ApplicationType ["
                            + ApplicationType.TEST + "] Valid");
                }
            }
            ServerType serverType = ServerType.AUTO;
            if (nutBootTest != null && nutBootTest.serverType() != null && nutBootTest.serverType() != ServerType.AUTO) {
                serverType = nutBootTest.serverType();
            }
            Class<?> mainClass = c;
            while (mainClass.getDeclaredAnnotation(NutBootTest.class) == null) {
                mainClass = mainClass.getSuperclass();
            }
            NutConfiguration nutConfiguration = NutConfigurationBuilder.newInstance()
                    .setApplicationType(applicationType)
                    .setServerType(serverType)
                    .setMainClass(mainClass)
                    .setLogLevel(getLevel(nutBootTest))
                    .setLogHandlers("com.yeskery.nut.util.logging.NutLoggingConsoleHandler")
                    .build();
            return Nut.run(nutConfiguration, new String[0]);
        }, NutApplication.class);
    }

    /**
     * 获取扩展存储对象
     * @param context 扩展上下文
     * @return 扩展存储对象
     */
    private static ExtensionContext.Store getStore(ExtensionContext context) {
        return context.getRoot().getStore(TEST_CONTEXT_NAMESPACE);
    }

    /**
     * 获取日志级别
     * @param nutBootTest Nut测试注解
     * @return 日志级别
     */
    private static Level getLevel(NutBootTest nutBootTest) {
        if (nutBootTest == null || StringUtils.isEmpty(nutBootTest.logLevel())) {
            return Level.INFO;
        } else {
            try {
                return Level.parse(nutBootTest.logLevel());
            } catch (IllegalArgumentException e) {
                System.out.println("Wrong Level Config [" + nutBootTest.logLevel() + "] Current Level Is Set To [INFO]");
                return Level.INFO;
            }
        }
    }
}
